﻿namespace OpenComposite.Repository.Tracking
{
	using System;
	using System.Collections.Generic;
	using System.Data;
	using System.Data.SqlClient;
	using System.Data.SqlTypes;
	using System.Runtime.InteropServices;
	using System.Text;
	using System.Workflow.Runtime;
	using System.Workflow.Runtime.Tracking;
	using System.Xml;

	public sealed class TrackingQuery
	{
		#region Initialization

		public TrackingQuery()
		{
		}

		public TrackingQuery(string connectionString)
		{
			if ( connectionString == null ) {
				throw new ArgumentNullException("connectionString");
			}
			_connectionString = connectionString;
		}

		#endregion Initialization

		#region Public Members

		#region Properties

		public string ConnectionString
		{
			get
			{
				return _connectionString;
			}
			set
			{
				if ( value == null ) {
					throw new ArgumentNullException("value");
				}
				_connectionString = value;
			}
		}

		#endregion Properties

		#region Methods

		public IList<TrackingWorkflowInstance> GetWorkflows(TrackingQueryOptions options)
		{
			if ( options == null ) {
				throw new ArgumentNullException("options");
			}
			if ( options.TrackingDataItems != null ) {
				foreach ( TrackingDataItemValue itemValue in options.TrackingDataItems ) {
					if ( itemValue.QualifiedName == null ) {
						throw new ArgumentNullException("options.TrackingDataItems.QualifiedName");
					}
					if ( itemValue.FieldName == null ) {
						throw new ArgumentNullException("options.TrackingDataItems.FieldName");
					}
				}
			}
			SqlCommand command = buildCommand(options);
			SqlDataReader reader = null;
			List<TrackingWorkflowInstance> list = new List<TrackingWorkflowInstance>();
			try {
				command.Connection = getConnection();
				reader = command.ExecuteReader(CommandBehavior.CloseConnection);
				while ( reader.Read() ) {
					TrackingWorkflowInstance instance = buildInstance(reader, options);
					if ( instance != null ) list.Add(instance);
				}
			} finally {
				if ( reader != null ) {
					reader.Close();
				}
				if ( ( ( command != null ) && ( command.Connection != null ) ) && ( command.Connection.State != ConnectionState.Closed ) ) {
					command.Connection.Close();
				}
			}
			return list;
		}

		public bool TryGetWorkflow(Guid workflowInstanceId, out TrackingWorkflowInstance workflowInstance)
		{
			bool success;
			SqlCommand command = buildCommand(workflowInstanceId);
			SqlDataReader reader = null;
			workflowInstance = null;
			try {
				command.Connection = getConnection();
				reader = command.ExecuteReader(CommandBehavior.CloseConnection);
				if ( reader.Read() ) {
					workflowInstance = buildInstance(reader, null);
					return true;
				}
				success = false;
			} finally {
				if ( reader != null ) {
					reader.Close();
				}
				if ( ( ( command != null ) && ( command.Connection != null ) ) && ( command.Connection.State != ConnectionState.Closed ) ) {
					command.Connection.Close();
				}
			}
			return success;
		}

		#endregion Methods

		#endregion Public Members

		#region Internal Members

		#region Static Methods

		internal static TrackingWorkflowInstance BuildInstance(SqlDataReader reader, string connectionString, TrackingQueryOptions options)
		{
			if ( !reader.IsDBNull(6) && options != null ) {
				string typeName = reader.GetString(6);
				if ( !string.IsNullOrEmpty(typeName) && !string.IsNullOrEmpty(options.WorkflowName) ) {
					typeName = typeName.Substring(typeName.LastIndexOf('.') + 1);
					if ( !typeName.ToLower().Contains(options.WorkflowName.ToLower()) ) return null;
				}
			}
			if ( reader == null ) {
				throw new ArgumentNullException("reader");
			}
			if ( reader.IsClosed ) {
				throw new ArgumentException("Invalid SqlDataReader", "reader.IsClosed");
			}
			TrackingWorkflowInstance instance = new TrackingWorkflowInstance(connectionString)
			{
				WorkflowInstanceId = reader.GetGuid(1),
				WorkflowInstanceInternalId = reader.GetInt64(2),
				Initialized = reader.GetDateTime(3)
			};
			if ( DBNull.Value == reader[4] ) {
				instance.InvokingWorkflowInstanceId = Guid.Empty;
			} else {
				instance.InvokingWorkflowInstanceId = reader.GetGuid(4);
			}
			instance.Status = (WorkflowStatus)reader.GetInt32(5);
			if ( !reader.IsDBNull(6) ) {
				instance.WorkflowTypeName = reader.GetString(6);
				instance.WorkflowAssemblyName = reader.GetString(7);
				instance.WorkflowFullTypeName = instance.WorkflowTypeName + ", " + reader.GetString(7);
			}
			return instance;
		}

		#endregion Static Methods

		#endregion Internal Members

		#region Private Members

		#region Fields

		private string _connectionString;

		#endregion Fields

		#region Methods

		private void buildArtifactParameters(SqlCommand cmd, IList<TrackingDataItemValue> artifacts)
		{
			if ( ( artifacts != null ) && ( artifacts.Count != 0 ) ) {
				StringBuilder output = new StringBuilder();
				XmlWriter writer = XmlWriter.Create(output);
				try {
					writer.WriteStartDocument();
					writer.WriteStartElement("TrackingDataItems");
					foreach ( TrackingDataItemValue value in artifacts ) {
						writer.WriteStartElement("TrackingDataItem");
						writer.WriteElementString("QualifiedName", value.QualifiedName);
						writer.WriteElementString("FieldName", value.FieldName);
						if ( value.DataValue != null ) {
							writer.WriteElementString("DataValue", value.DataValue);
						}
						writer.WriteEndElement();
					}
					writer.WriteEndElement();
					writer.WriteEndDocument();
				} finally {
					writer.Flush();
					writer.Close();
				}
				cmd.Parameters.Add(new SqlParameter("@TrackingDataItems", output.ToString()));
			}
		}

		private SqlCommand buildCommand(Guid workflowInstanceId)
		{
			SqlCommand command = new SqlCommand("[dbo].[GetWorkflows]");
			command.CommandType = CommandType.StoredProcedure;
			command.Parameters.Add(new SqlParameter("@WorkflowInstanceId", workflowInstanceId));
			return command;
		}

		private SqlCommand buildCommand(TrackingQueryOptions options)
		{
			SqlCommand cmd = new SqlCommand("[dbo].[GetWorkflows]");
			cmd.CommandType = CommandType.StoredProcedure;

			SqlParameter parameter = new SqlParameter();
			if ( options.WorkflowStatus.HasValue ) {
				cmd.Parameters.Add(new SqlParameter("@WorkflowStatusId", options.WorkflowStatus.Value));
				if ( ( DateTime.MinValue != options.StatusMinDateTime ) || ( DateTime.MaxValue != options.StatusMaxDateTime ) ) {
					cmd.Parameters.Add(new SqlParameter("@StatusMinDateTime",
						options.StatusMinDateTime < SqlDateTime.MinValue.Value ? SqlDateTime.MinValue.Value : options.StatusMinDateTime));
					cmd.Parameters.Add(new SqlParameter("@StatusMaxDateTime",
						options.StatusMaxDateTime > SqlDateTime.MaxValue.Value ? SqlDateTime.MaxValue.Value : options.StatusMaxDateTime));
				} else {
					cmd.Parameters.Add(new SqlParameter("@StatusMinDateTime", SqlDateTime.MinValue));
					cmd.Parameters.Add(new SqlParameter("@StatusMaxDateTime", SqlDateTime.MaxValue));
	}
			}
			if ( options.WorkflowType != null ) {
				parameter = new SqlParameter("@TypeFullName", options.WorkflowType.FullName);
				parameter.SqlDbType = SqlDbType.NVarChar;
				cmd.Parameters.Add(parameter);

				parameter = new SqlParameter("@AssemblyFullName", options.WorkflowType.Assembly.FullName);
				parameter.SqlDbType = SqlDbType.NVarChar;
				cmd.Parameters.Add(parameter);
			}
			if ( ( options.TrackingDataItems != null ) && ( options.TrackingDataItems.Count > 0 ) ) {
				buildArtifactParameters(cmd, options.TrackingDataItems);
			}
			return cmd;
		}

		private TrackingWorkflowInstance buildInstance(SqlDataReader reader, TrackingQueryOptions options)
		{
			return BuildInstance(reader, _connectionString, options);
		}

		private SqlConnection getConnection()
		{
			if ( _connectionString == null ) {
				throw new InvalidOperationException("ConnectionString is missing.");
			}
			SqlConnection connection = new SqlConnection(_connectionString);
			connection.Open();
			return connection;
		}

		#endregion Methods

		#endregion Private Members
	}
}